package com.vhans.db.service.impl;

import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vhans.core.exception.ServiceException;
import com.vhans.core.strategy.context.UploadStrategyContext;
import com.vhans.core.utils.FileUtils;
import com.vhans.core.domain.AFile;
import com.vhans.db.mapper.AFileMapper;
import com.vhans.db.model.dto.ConditionDTO;
import com.vhans.db.model.dto.file.FolderDTO;
import com.vhans.db.model.vo.file.FileVO;
import com.vhans.db.service.IAFileService;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static com.vhans.core.constant.CommonConstant.FALSE;
import static com.vhans.core.constant.CommonConstant.TRUE;

/**
 * 文件业务接口实现类
 *
 * @author vhans
 */
@Service
public class AFileServiceImpl extends ServiceImpl<AFileMapper, AFile> implements IAFileService {

    /**
     * 本地路径
     */
    @Value("${upload.local.path}")
    private String localPath;

    @Autowired
    private AFileMapper fileMapper;

    @Autowired
    private HttpServletResponse response;

    @Autowired
    private UploadStrategyContext uploadStrategyContext;

    @Override
    public List<FileVO> listFileVOList(ConditionDTO condition) {
        return fileMapper.selectFileVOList(condition.getFilePath());
    }

    @Override
    public void uploadFile(MultipartFile file, String path) {
        try {
            String uploadPath = "/".equals(path) ? path : path + "/";
            // 上传文件
            String url = uploadStrategyContext.executeUploadStrategy(file, uploadPath);
            // 获取文件md5值和扩展名
            String md5 = FileUtils.getMd5(file.getInputStream());
            String extName = FileUtils.getExtension(file);
            AFile existFile = fileMapper.selectOne(new LambdaQueryWrapper<AFile>()
                    .select(AFile::getId)
                    .eq(AFile::getFileName, md5)
                    .eq(AFile::getFilePath, path));
            Assert.isNull(existFile, "文件已存在");
            // 保存文件信息
            AFile newFile = AFile.builder()
                    .fileUrl(url)
                    .fileName(md5)
                    .filePath(path)
                    .extendName(extName)
                    .fileSize((int) file.getSize())
                    .isDir(FALSE)
                    .build();
            fileMapper.insert(newFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createFolder(FolderDTO folder) {
        String fileName = folder.getFileName();
        String filePath = folder.getFilePath();
        // 判断目录是否存在
        AFile file = fileMapper.selectOne(new LambdaQueryWrapper<AFile>()
                .select(AFile::getId)
                .eq(AFile::getFilePath, folder.getFilePath())
                .eq(AFile::getFileName, fileName));
        Assert.isNull(file, "目录已存在");
        // 创建目录
        File directory = new File(localPath + filePath + "/" + fileName);
        if (FileUtils.mkdir(directory)) {
            AFile newAFile = AFile.builder()
                    .fileName(fileName)
                    .filePath(filePath)
                    .isDir(TRUE)
                    .build();
            fileMapper.insert(newAFile);
        } else {
            throw new ServiceException("创建目录失败");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteFile(List<Integer> fileIdList) {
        List<AFile> aFiles = fileMapper.selectList(new LambdaQueryWrapper<AFile>()
                .select(AFile::getFileName, AFile::getFilePath, AFile::getExtendName, AFile::getIsDir)
                .in(AFile::getId, fileIdList));
        // 删除数据库中的文件信息
        fileMapper.deleteBatchIds(fileIdList);
        aFiles.forEach(aFile -> {
            File file;
            String fileName = localPath + aFile.getFilePath() + "/" + aFile.getFileName();
            if (aFile.getIsDir().equals(TRUE)) {
                // 删除数据库中目录下的子文件
                String filePath = aFile.getFilePath() + aFile.getFileName();
                fileMapper.delete(new LambdaQueryWrapper<AFile>().eq(AFile::getFilePath, filePath));
                // 删除的目录
                file = new File(fileName);
                if (file.exists()) {
                    FileUtils.deleteFile(file);
                }
            } else {
                // 删除文件
                file = new File(fileName + "." + aFile.getExtendName());
                if (file.exists()) {
                    file.delete();
                }
            }
        });
    }

    @Override
    public void downloadFile(Integer fileId) {
        // 查询文件信息
        AFile aFile = fileMapper.selectOne(new LambdaQueryWrapper<AFile>()
                .select(AFile::getFilePath, AFile::getFileName,
                        AFile::getExtendName, AFile::getIsDir)
                .eq(AFile::getId, fileId));
        Assert.notNull(aFile, "文件不存在");
        String filePath = localPath + aFile.getFilePath() + "/";
        // 要下载的不是目录
        if (aFile.getIsDir().equals(FALSE)) {
            String fileName = aFile.getFileName() + "." + aFile.getExtendName();
            downloadFile(filePath, fileName);
        } else {
            // 下载的是目录则压缩下载
            String fileName = filePath + aFile.getFileName();
            File src = new File(fileName);
            File dest = new File(fileName + ".zip");
            try {
                ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(dest));
                // 压缩文件
                toZip(src, zipOutputStream, src.getName());
                zipOutputStream.close();
                // 下载压缩包
                downloadFile(filePath, aFile.getFileName() + ".zip");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (dest.exists()) {
                    dest.delete();
                }
            }
        }
    }

    @Override
    public String uploadStrategyFile(MultipartFile file, String path, String fileType) {
        // 上传文件
        String url = uploadStrategyContext.executeUploadStrategy(file, path);
        try {
            // 获取文件md5值和扩展名
            String md5 = FileUtils.getMd5(file.getInputStream());
            String extName = FileUtils.getExtension(file);
            // 判断是否为支持的文件类型
            List<String> supportedExtensions = new ArrayList<>();
            if ("image".equals(fileType)) {
                supportedExtensions = Arrays.asList("jpg", "jpeg", "png", "gif");
            } else if ("video".equals(fileType)) {
                supportedExtensions = Arrays.asList("mp4", "avi", "wmv", "mov");
            }
            if (!supportedExtensions.contains(extName.toLowerCase())) {
                throw new ServiceException("不支持的文件类型");
            }
            AFile existFile = fileMapper.selectOne(new LambdaQueryWrapper<AFile>()
                    .select(AFile::getId)
                    .eq(AFile::getFileName, md5)
                    .eq(AFile::getFilePath, path));
            if (Objects.isNull(existFile)) {
                // 保存文件信息
                AFile newFile = AFile.builder()
                        .fileUrl(url)
                        .fileName(md5)
                        .filePath(path)
                        .extendName(extName)
                        .fileSize((int) file.getSize())
                        .isDir(FALSE)
                        .build();
                fileMapper.insert(newFile);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return url;
    }

    /**
     * 下载文件
     *
     * @param filePath 文件路径
     * @param fileName 文件名
     */
    private void downloadFile(String filePath, String fileName) {
        FileInputStream fileInputStream = null;
        ServletOutputStream outputStream = null;
        try {
            // 设置文件名
            response.addHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
            fileInputStream = new FileInputStream(filePath + fileName);
            outputStream = response.getOutputStream();
            IOUtils.copyLarge(fileInputStream, outputStream);
        } catch (IOException e) {
            throw new ServiceException("文件下载失败");
        } finally {
            IOUtils.closeQuietly(fileInputStream);
            IOUtils.closeQuietly(outputStream);
        }
    }

    /**
     * 压缩文件夹
     *
     * @param src             源文件
     * @param zipOutputStream 压缩输出流
     * @param name            文件名
     * @throws IOException IO异常
     */
    private static void toZip(File src, ZipOutputStream zipOutputStream, String name) throws IOException {
        for (File file : Objects.requireNonNull(src.listFiles())) {
            if (file.isFile()) {
                // 判断文件，变成ZipEntry对象，放入到压缩包中
                ZipEntry zipEntry = new ZipEntry(name + "/" + file.getName());
                // 读取文件中的数据，写到压缩包
                zipOutputStream.putNextEntry(zipEntry);
                FileInputStream fileInputStream = new FileInputStream(file);
                int b;
                while ((b = fileInputStream.read()) != -1) {
                    zipOutputStream.write(b);
                }
                fileInputStream.close();
                zipOutputStream.closeEntry();
            } else {
                toZip(file, zipOutputStream, name + "/" + file.getName());
            }
        }
    }
}




